﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

#pragma warning disable 1591

namespace OpGen.Compiler.Generator.CS
{
    public class CSCodeWriter : CodeWriter
    {
        public CSCodeWriter(Stream Stream)
            : base(Stream)
        {
            UseBlockSpace = true;
            AutoNewLine = true;
        }

        public override void WriteTitleInfo(ProjectElement element)
        {
            WriteLine("/* OPGEN Compiler v{1} - {0}", element.Projectname, CompilerVersion.Version());
            WriteLine(" * Genreated on {0}", DateTime.Now);
            WriteLine(" * --------------------------------------");
            WriteLine(" */");
            WriteLine("");
        }

        public override void WriteImports(ProjectElement element)
        {
            Write("using System;");
            Write("using System.Collections.Generic;");
            Write("using System.Linq;");
            Write("using System.Text;");
            Write("using System.IO;");
            Write("");

            if (element.UseExtension)
            {
                Write("using OpGen.Extensions;");
                Write("");
            }
        }

        public override void BeginNamespace(NamespaceElement element)
        {
            Write("namespace {0}", element.Elementname);
            BeginBlock("{{");
        }

        public override void BeginNamespace(ProjectElement element)
        {
            Write("namespace {0}", element.CodeEnumNamespace);
            BeginBlock("{{");
        }

        public override void EndNamespace()
        {
            EndBlock("}}");
        }

        public override void BeginClass(ClassElement element, bool useExtensions)
        {
            if (useExtensions)
                Write("{0} class {1} : IOpGenClass", GetModifier(element), element.Elementname);
            else
                Write("{0} class {1}", GetModifier(element), element.Elementname);

            BeginBlock("{{");
        }

        public override void EndClass()
        {
            EndBlock("}}");
        }

        public override void BeginEnum(EnumElement element)
        {
            Write("{0} enum {1} : int", GetModifier(element), element.Elementname);
            BeginBlock("{{");
        }

        public override void BeginEnum(ProjectElement Element)
        {
            Write("{0} enum {1} : int", GetModifier(Element), Element.Elementname);
            BeginBlock("{{");
        }

        public override void EnumItem(EnumElement element)
        {
            var list = new List<string>();

            foreach (EnumItemElement item in element.ChildElements)
            {
                list.Add(string.Format("{0} = {1}", item.Elementname, item.Value));
            }

            if (AutoNewLine)
                Write(string.Join(",\n", list.ToArray()));
            else
                Write(string.Join(",", list.ToArray()));
        }

        public override void EndEnum()
        {
            EndBlock("}}");
        }

        public override void BeginMessage(MessageElement element, bool useExtensions)
        {
            if (useExtensions)
                Write("{0} class {1} : IOpGenMessage", GetModifier(element), element.Elementname);
            else
                Write("{0} class {1}", GetModifier(element), element.Elementname);

            BeginBlock("{{");

            Write("public int OpCode {{ get {{ return {0}; }} }}", element.Code);
        }

        public override void EndMessage()
        {
            EndBlock("}}");
        }

        public override void BeginField(FieldElement element)
        {
            if (element.Type.Type == Types.REFERENCE)
            {
                Write("private {0} _{1} = new {0}();", GetTypeName(element), element.Elementname);
            }
            else
            {
                Write("private {0} _{1};", GetTypeName(element), element.Elementname);
            }
        }

        public override void EndField(FieldElement element)
        {
            return;
        }

        public override void BeginProperty(FieldElement element)
        {
            if (element.IsVirtual)
                Write("{0} virtual {1} {2}", GetModifier(element), GetTypeName(element), element.Elementname);
            else
                Write("{0} {1} {2}", GetModifier(element), GetTypeName(element), element.Elementname);

            BeginBlock("{{");
        }

        public override void GetterProperty(FieldElement element)
        {
            Write("get {{ return _{0}; }}", element.Elementname);
        }

        public override void SetterProperty(FieldElement element)
        {
            Write("set {{ _{0} = value; }}", element.Elementname);
        }

        public override void EndProperty(FieldElement element)
        {
            EndBlock("}}");
        }

        public override void Constructor(Element element)
        {
            #region Leerer Konstruktor
            Write("public {0}() {{ }}", element.Elementname);
            #endregion

            #region ByteArray Konstruktor
            Write("public {0}(byte[] byteArray)", element.Elementname);
            BeginBlock("{{");

            if (element.ElementType == ElementTypes.Message)
                Write("if (BitConverter.ToInt32(byteArray, 4) != OpCode) throw new Exception(\"Invalide OpCode\");");

            Write("using (var BR = new BinaryReader(new MemoryStream(byteArray)))");
            BeginBlock("{{");

            Write("ReadBR(BR);");

            EndBlock("}}");
            EndBlock("}}");
            #endregion

            #region BinaryReader Konstruktor
            Write("public {0}(BinaryReader BR)", element.Elementname);
            BeginBlock("{{");

            Write("ReadBR(BR);");
            EndBlock("}}");
            #endregion
        }

        public override void Methods(Element element, bool useExtensions)
        {
            #region ToArray
            Write("public byte[] ToArray()");
            BeginBlock("{{");
            Write("using (var MS = new MemoryStream())");
            BeginBlock("{{");
            Write("using (var BW = new BinaryWriter(MS))");
            BeginBlock("{{");

            if (element.ElementType == ElementTypes.Message)
                Write("BW.Write(OpCode);");

            foreach (FieldElement Field in element.ChildElements)
            {
                if (Field.Type.Type == Types.REFERENCE)
                {
                    if (Field.Type.ReferenceElement.ElementType == ElementTypes.Enum)
                    {
                        Write("BW.Write((int)_{0});", Field.Elementname);
                    }
                    else
                    {
                        if (Field.Type.IsList)
                        {
                            Write("for (int i = 0; i < _{0}.Count; i++)", Field.Elementname);
                            BeginBlock("{{");
                            Write(GetBinaryWriterMethodeName("BW", Field, "i"));
                            EndBlock("}}");

                        }
                        else
                        {
                            Write(GetBinaryWriterMethodeName("BW", Field, ""));
                        }
                    }
                }
                else
                {
                    if (Field.Type.IsList)
                    {
                        Write("for (int i = 0; i < _{0}.Count; i++)", Field.Elementname);
                        BeginBlock("{{");
                        Write(GetBinaryWriterMethodeName("BW", Field, "i"));
                        EndBlock("}}");
                    }
                    else
                    {
                        Write(GetBinaryWriterMethodeName("BW", Field, ""));
                    }
                }

            }

            Write("BW.Flush();");
            Write("return MS.ToArray();");

            EndBlock("}}");
            EndBlock("}}");
            EndBlock("}}");
            #endregion

            #region ReadBR
            Write("protected internal void ReadBR(BinaryReader BR)");
            BeginBlock("{{");

            Write("BR.ReadInt32();");

            foreach (FieldElement Field in element.ChildElements)
            {
                if (Field.Type.Type == Types.REFERENCE)
                {
                    if (Field.Type.ReferenceElement.ElementType == ElementTypes.Enum)
                    {
                        Write("_{0} = ({1})BR.ReadInt32();", Field.Elementname, Field.Type.ReferenceElement.Fullname);
                    }
                    else
                    {
                        if (Field.Type.IsList)
                        {
                            Write("for (int i = 0; i < BR.ReadInt32(); i++)");
                            BeginBlock("{{");
                            Write("_{0}.Add(new {1}(BR));", Field.Elementname, Field.Type.ReferenceElement.Fullname);
                            EndBlock("}}");

                        }
                        else
                        {
                            Write("_{0} = new {1}(BR);", Field.Elementname, Field.Type.ReferenceElement.Fullname);
                        }
                    }
                }
                else
                {
                    if (Field.Type.IsList)
                    {
                        Write("for (int i = 0; i < BR.ReadInt32(); i++)");
                        BeginBlock("{{");
                        Write("_{0}.Add({1});", Field.Elementname, GetBinaryReaderMethodeName("BR", "ByteArray", Field));
                        EndBlock("}}");

                    }
                    else
                    {
                        Write("_{0} = {1};", Field.Elementname, GetBinaryReaderMethodeName("BR", "ByteArray", Field));
                    }
                }
            }

            EndBlock("}}");
            #endregion
        }
    }
}
